前言
1 | 本来是没想把这个洞单独写篇文章的,因为也看到其他师傅已经发表了相应的文章。主要的绕过原理也比较简单。即使用正则表达式匹配路径时.*不会匹配\r或\n,因此当url中包含换行符则springsecurity使用RegexRequestMatcher(".*",null)正则表达式去匹配就会匹配不到从而导致了权限绕过。 |
问题
借用killer师傅文章中的Demo漏洞环境如下:
SpringSecurity
配置
1 | protected void configure(HttpSecurity http) throws Exception { |
Controller
配置
1 |
|
正常访问被SpringSecurity规则拦截,如下所示:
通过%0a绕过,这里和其他师傅们的分析有点不同,虽然绕过了springsecurity的限制但是并没有访问到Controller。
因为不止一个师傅用上面的demo代码做了复现,所以刚开始我以为是自己哪里配置错了,但是反复的检查发现配置没有问题,我也确实绕过了springsecurity的检测,只是没有访问到Controller而已。后来找killer师傅要了他的环境,在绕过以后确实是可以请求到的。
分析
对比了两套环境的差异,发现主要的区别在springmvc的版本上,而且其实根据结果我们也可以猜出来。在第一套环境中springmvc再匹配由/test/11%0a
的路由没匹配到而第二套环境匹配到了。所以我们深入的分析下到底是什么原因导致了这种差异。
通过调试对比发现在DispatcherServlet#doDispatch
中获取的mappedHandler
是不同的,所以问题出现在getHandler
的处理过程中。
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
循环调用保存的HandlerMapping#getHandler
。
1 | protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
默认保存了下面5个HandlerMapping
,通过简单查阅文档可知注解中配置的路由由RequestMappingHandlerMapping
处理。
getHandler
中通过getHandlerInternal
获取handler构建HandlerExecutionChain
并返回。
1 | public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
getHandlerInternal
从request对象中获取请求的path并根据path找到handlerMethod
。
1 | protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { |
lookupHandlerMethod
首先直接根据路径获取获取不到才使用addMatchingMappings
遍历所有的ReuqestMappingInfo
对象并进行匹配。
1 | protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { |
1 | private void addMatchingMappings(Collection<T> mappings, List<AbstractHandlerMethodMapping<T>.Match> matches, HttpServletRequest request) { |
在getMatchingMapping
中不同版本的SpringMVC代码不太一样:
springmvc 5.3.20:
1 | public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { |
springmvc 5.1.9:
1 | public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { |
也就是不同版本的springmvc使用了不同的RequestCondition
导致的。经过简单的查阅资料PathPatternsRequestCondition
是spring高版本引入的使用PathPattern
来进行URL匹配。而在早些版本PatternsRequestCondition
使用AntPathMatcher
来进行匹配。
AntPathMatcher
主要是由AntPathMatcher#doMatch
完成匹配,首先通过tokenizePattern
对路由中的配置的url进行分割,再调用tokenizePath
对传入的PathUrl
分割,最后将分割后的结果分别通过matchStrings
进行匹配。
当匹配后面的换行时,由于这里得到的正则也是.*
所以也匹配不到换行符,因此找不到对应的Controller进行处理。
PathPattern
PathPatternsRequestCondition#getMatchingCondition
将请求的URL转换为PathContainer
对象并调用getMatchingPatterns
进行匹配,匹配成功则创建PathPatternsRequestCondition
并返回。
遍历PathPattern
并进行匹配
通过SeparatorPathElement#matches
匹配,SeparatorPathElement
是分离器元素,默认是/
。
继续往下调用LiteralPathElement#matches
逐个字符匹配。
由于PathContainer
和PathPattern
中这里都保存的是test
所以可以通过检测。
后面还有元素所以继续matches
最后针对*
通过WildcardPathElement
进行匹配。
在WildcardPathElement
中只要pathElements的元素个数和PathPattern
中的元素个数一致都会返回true。而元素个数的是由/
分割的,换行符
对元素分割没有影响,因此可以正常匹配。
总结
SpringSecurity的权限认证绕过只能在高版本的springmvc中使用,由于低版本使用AntPathMatcher
也是通过正则.*
来匹配的URL路径的,因此在绕过SpringSecurity
权限认证的同时也会绕过springmvc
路由的匹配,导致匹配失败。